package testrank;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.Stack;
import java.util.TreeMap;

import testrank.TestRankConstants.TestSet;


/**
 * @author Hagai Cibulski
 */
public class CombinationOptimizer {

	public static int MIN_AFFIN = 80;//0;
	public static int MAX_AFFIN = 100;
	
	public static int MIN_DYNAMIC = 0;
	public static int MAX_DYNAMIC = 20;//100;
	
	
//	private static final boolean VERBOSE = false;

	private static final int N_MUTATIONS = 8;
	private static final String BASE_PATH = TestRankConstants.TEST_SET == TestSet.Log4J ? 
				"E:/Dev/db2_3/" :
				"E:/Dev/db5/";
//	private static final String STR_PATTERN = "%-50s";
//	private static List<String> PREDICTORS = Arrays.asList("AFFIN", "CLCNT", "VALPROP");

	private static List<Integer> PERCENTILES = Arrays.asList(80);//, 75, 70, 60, 50);
	
	
	private static Map<Conflict, Map<List<Double>, Double>> conflict2weights2score = new HashMap<Conflict, Map<List<Double>, Double>>();
	private static Map<String, Set<String>> method2coveringTests = new HashMap<String, Set<String>>();
	
	private static Set<List<Double>> intersection = null;
	private static Set<List<Double>> intersection2 = null;
	private static List<List<Double>> lastArgsMin = null;
	
	public static void main(String[] args) throws IOException {
	
		String fileName = BASE_PATH + "all_scores.properties";
		BufferedReader input = new BufferedReader(new FileReader(fileName));
		
		Properties p = new Properties();
		p.load(input);

		// method@test@predictor=score
		
//		Map <Conflict, Double> conflict2score = new HashMap<Conflict, Double>();
		Map <Conflict, Map<String, Double>> conflict2pred2score = new HashMap<Conflict, Map<String, Double>>();
		
		for (Object k : p.keySet()) {
			String[] ks = ((String)k).split("@");
			Conflict c = new Conflict(ks[0], ks[1]);
			Double score = Double.parseDouble((String)p.get(k));
//			conflict2score.put(c, score);
			
			String pred = ks[2];
			Map<String, Double> pred2score = conflict2pred2score.get(c);
			if (pred2score == null) {
				pred2score = new HashMap<String, Double>();
				conflict2pred2score.put(c, pred2score);
			}
			pred2score.put(pred, score);
			
			
			if (pred.equals("EXCNT") && score > 0.0) {
				Set<String> coveringTests = method2coveringTests.get(c.getMethodSignature());
				if (coveringTests == null) {
					coveringTests = new HashSet<String>();
					method2coveringTests.put(c.getMethodSignature(), coveringTests);
				}
				coveringTests.add(c.getTestSignature());
			}
		}
//		System.out.println(conflict2score.size());
		
		for (Conflict c : conflict2pred2score.keySet()) {
			Map<String, Double> pred2score = conflict2pred2score.get(c);
			
			double s0 = pred2score.get("AFFPROP");
			double s1 = pred2score.get("CLCNT");
			double s2 = pred2score.get("VALPROP");
			double s3 = pred2score.get("STKEXCNT");
			
			for (int wh = MIN_AFFIN; wh <= MAX_AFFIN; wh++) {
				double w0 = wh/100.0;
				for (int wi = MIN_DYNAMIC; //wi <= 100-wh; wi++) {
											wi <= Math.min(MAX_DYNAMIC, 100-wh); wi++) {
					double w1 = wi/100.0;
					for (int wj = MIN_DYNAMIC; //wj <= 100-wh-wi; wj++) { // 
												wj <= Math.min(MAX_DYNAMIC, 100-wh-wi); wj++) {
						double w2 = wj/100.0;
						int wk = 100 - wh - wi - wj;
						double w3 = wk/100.0;
						//System.out.println(w1+","+w2+","+w3+",");					
						//Predictor cp = new AveragePredictor(w1, w2, w3, wi+","+wj);
						double s = s0*w0+s1*w1+s2*w2+s3*w3;
						Map<List<Double>, Double> w2s = conflict2weights2score.get(c);
						if (w2s == null) {
							w2s = new HashMap<List<Double>, Double>();
							conflict2weights2score.put(c, w2s);
						}
						w2s.put(Arrays.asList(w0,w1,w2,w3), s);
					}
				}
			}
		}

		TestManager.load(); // for locator

		searchMin();
		//testWeights();
	}


	private static void searchMin() {
		for (double p1 = 80; p1 >= 80; p1 -= 10) {
			search(p1/100.0);
		}
	}
	

	public static void search(double percentile) {		
		List<List<Double>> argsMin = new ArrayList<List<Double>>();
		double min = 2.0;
		for (int wh = MIN_AFFIN; wh <= MAX_AFFIN; wh++) {
			for (int wi = MIN_DYNAMIC; //wi <= 100-wh; wi++) {
										wi <= Math.min(MAX_DYNAMIC, 100-wh); wi++) {
				for (int wj = MIN_DYNAMIC; //wj <= 100-wh-wi; wj++) { //
											wj <= Math.min(MAX_DYNAMIC, 100-wh-wi); wj++) {
					double wk = 100.0 - wh - wi - wj;
					List<Double> w = Arrays.asList(
							wh/100.0,
							wi/100.0,
							wj/100.0,
							wk/100.0
					);
					
					double p = calcPercentile(percentile, w);
					if (p < min) {
						min = p;
						argsMin = new ArrayList<List<Double>>();
						argsMin.add(w);
						//System.out.println("New min: " + p + ", arg: " + w);
					}
					else if (p == min) {
						argsMin.add(w);
					}
					else if (p > min) {
						//System.out.println("Not min: " + p + ", arg: " + w);
					}
				}
			}
		}
		
		System.out.println("\npercentile="+percentile);
		System.out.println("min="+min);
		System.out.println("argsMin="+argsMin);
		System.out.println("argsMin.size()="+argsMin.size());
		
		if (intersection == null) {
			intersection = new HashSet<List<Double>>();
			intersection.addAll(argsMin);

			intersection2 = new HashSet<List<Double>>();
			intersection2.addAll(argsMin);
		}
		else {
			intersection.retainAll(argsMin);
				
			intersection2.clear();
			intersection2.addAll(argsMin);
			intersection2.retainAll(lastArgsMin);
		}
		lastArgsMin = argsMin;

		printCenter(argsMin);

		
		//System.out.println("intersection="+intersection);
		
//		printIntersection(intersection, argsMin);
//		printIntersection(intersection2, argsMin);
//		plotIntersection(argsMin);
		
//		System.out.println("intersection.size()="+intersection.size());
//		System.out.println("intersection2.size()="+intersection2.size());
	}

	
	@SuppressWarnings("unused")
	private static void testWeights() {
		System.out.printf("%-18.18s    ", "weights");
		for (int p1 : PERCENTILES) {
		//for (double p1 = 80; p1 >= 50; p1 -= 10) {
			System.out.printf("%-5.5s    ", p1);
		}
		System.out.println();

		
		testWeights(0.0, 0.0, 1.0);
		testWeights(0.0, 0.01, 0.99);
		testWeights(0.0, 0.02, 0.98);
		testWeights(0.0, 0.03, 0.97);
		testWeights(0.0, 0.04, 0.96);
		testWeights(0.0, 0.15, 0.85);
		testWeights(0.0, 0.16, 0.84);
		
		testWeights(0.01, 0.00, 0.99);
		testWeights(0.01, 0.01, 0.98);
		testWeights(0.01, 0.02, 0.97);
		testWeights(0.01, 0.03, 0.96);
		testWeights(0.01, 0.14, 0.85);
		testWeights(0.01, 0.15, 0.84);
		
		testWeights(0.02, 0.00, 0.98);
		testWeights(0.02, 0.01, 0.97);
		testWeights(0.02, 0.14, 0.84);
		
		testWeights(0.03, 0.13, 0.84);

		testWeights(0.04, 0.12, 0.84);
	}


	private static void testWeights(Double... wa) {
		List<Double> w = Arrays.asList(wa);
		System.out.printf("%-18.18s    ", w);
		for (int p1 : PERCENTILES) {
		//for (double p1 = 80; p1 >= 50; p1 -= 10) {
			double percentile = p1/100.0;
			double p = calcPercentile(percentile, w);
			System.out.printf("%-5.5s    ", p);
		}
		System.out.println();
	}

	@SuppressWarnings("unused")
	private static void plotIntersection(List<List<Double>> argsMin) {
		System.out.print("intersection=");
		for (List<Double> l : argsMin) {
			if (intersection.contains(l)) {
				System.out.println(l.get(0) + "\t" + l.get(1));
			}
		}
		System.out.println();
	}

	private static void printCenter(List<List<Double>> argsMin) {
		double[] center = new double[argsMin.get(0).size()];
		for (List<Double> l : argsMin) {
			for (int i = 0; i < l.size(); i++) {
				center[i] += l.get(i);
			}
		}
//		System.out.println("center: " + Arrays.asList(center));
		System.out.println("center: ");
		for (int i = 0; i < center.length; i++) {
			System.out.println(center[i]/argsMin.size());
		}
	}

	@SuppressWarnings("unused")
	private static void printIntersection(Set<List<Double>> inter, List<List<Double>> argsMin) {
		System.out.print("intersection=");
		for (List<Double> l : argsMin) {
			if (inter.contains(l)) {
				System.out.print(l + ", ");
			}
		}
		System.out.println();
	}		
		
	//calcPercentile(w^):
	//
	//relativeRanks = []
	//for each mutatedMethod 
	// 	find actualFailedTests 
	//	T = {t : mutatedMethod@t@EXCNT > 0}
	// 	N = |T|-|actualFailedTests|+1
	//
	//	for each t in T 
	//		score(t) = conflict2weights2score.get(mutatedMethod@t@w^)
	//  SortedMap<Double, List<String>> rankedTests = T sorted by score(t) descending, grouped by score(t)  
	//
	//	rank = 0
	//  for each tests in rankedTests 
	//		find actualFailedTestsInRank
	//		rank += |tests| - |actualFailedTestsInRank|
	//		if |actualFailedTestsInRank| > 0
	//			relativeRanks.add((rank+1)/N)
	//
	//return relativeRanks.sorted().get(7)
	
	private static double calcPercentile(double percentile, List<Double> w) {
		List<Double> relativeRanks = new ArrayList<Double>();
		
		for (int i = 1; i <= N_MUTATIONS; i++) {
			Experiment experiment = new Experiment(BASE_PATH + "actual_" + i + ".txt");
			experiment.addRelativeRanks(relativeRanks, w);
		}
		Collections.sort(relativeRanks);
		
		int cutoff = (int)Math.round(((double)relativeRanks.size() * percentile));

		return relativeRanks.get(cutoff - 1);
	}
	


	static class Experiment {
		String sourceFileName;
		int line;
		String methodSignature;
		List<String> actualFailedTests;
		
		static List<String> readFile(String fileName) {
			List<String> lines = new ArrayList<String>();
			try {
				BufferedReader input = new BufferedReader(new FileReader(fileName));
				try {
					String line = null; // not declared within while loop
					while ((line = input.readLine()) != null) {
						lines.add(line);
					}
				} finally {
					input.close();
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
			return lines;
		}

		Experiment(String experimentFileName) {
			List<String> lines = readFile(experimentFileName);
			String line0 = lines.get(0);
			String[] location = line0.split(":");
			if (location.length != 2) {
				System.out.println("bad format in file " + experimentFileName
						+ ". expected file:line but was " + line0);
				return;
			}
			sourceFileName = location[0];
			String lineString = location[1];
			line = Integer.parseInt(lineString);

			methodSignature = TestManager.locator().locateMethod(
					sourceFileName, line);
			if (methodSignature == null) {
				System.out.println("No tests affected.");
				return;
			}
			
			if (!lines.get(1).equals(methodSignature)) {
				System.out.println("\nmethodSignature=" + methodSignature);
				System.out.println("expected methodSignature=" + lines.get(1));
				return;
			}
			//System.out.println("\nmethod=" + getShortSignature(methodSignature));

			actualFailedTests = new ArrayList<String>();
			for (String l : lines.subList(2, lines.size())) {
				actualFailedTests.add(l.replace(' ', '_'));
			}
			
			methodSignature = methodSignature.replace(' ', '_');
		}
		
		
		//	T = {t : mutatedMethod@t@EXCNT > 0}
		// 	N = |T|-|actualFailedTests|+1
		//
		//	for each t in T 
		//		score(t) = conflict2weights2score.get(mutatedMethod@t@w^)
		//  SortedMap<Double, List<String>> rankedTests = T sorted by score(t) descending, grouped by score(t)  
		//
		//	rank = 0
		//  for each tests in rankedTests 
		//		find actualFailedTestsInRank
		//		rank += |tests| - |actualFailedTestsInRank|
		//		if |actualFailedTestsInRank| > 0
		//			relativeRanks.add((rank+1)/N)
		public void addRelativeRanks(List<Double> relativeRanks, List<Double> w) {
			SortedMap<Double, List<String>> rankedTests = new TreeMap<Double, List<String>>();
			
			Set<String> coveringTests = method2coveringTests.get(methodSignature);
			int total = coveringTests.size() - actualFailedTests.size() + 1;
			for (String t : coveringTests) {
				Conflict c = new Conflict(methodSignature, t);
				Map<List<Double>, Double> x = conflict2weights2score.get(c);
				double score = x.get(w);
				List<String> tests = rankedTests.get(score);
				if (tests == null) {
					tests = new ArrayList<String>();
					rankedTests.put(score, tests);
				}
				tests.add(t);
			}
			
			Stack<Double> reverseKeys = new Stack<Double>();
			for (Double rank : rankedTests.keySet()) {
				reverseKeys.push(rank);
			}

			int rank = 0;
			while ( ! reverseKeys.isEmpty()) {			
				Double score = reverseKeys.pop();
				List<String> tests = rankedTests.get(score);
				List<String> actualFailedTestsInRank = new ArrayList<String>();
				for (String test : tests) { 
					for (String actualFailedTest : actualFailedTests) {
						if (actualFailedTest.equals(test)) {
							actualFailedTestsInRank.add(test);
						}
					}
				}
				int nonFailedTestsInRank = tests.size() - actualFailedTestsInRank.size();
				int best = rank + 1;
				rank += nonFailedTestsInRank;
				int worst = rank + 1;
				// relative position of this failure WRT other tests excluding other actual failed tests
				double absPosition = TestRankConstants.EQUIVALENT_SCORES_STRATEGY == TestRankConstants.EquivalentScoresStrategy.WorstCase ? 
					worst :
					(best + worst) / 2.0;
				double relativePosition = absPosition / total;  
				for (@SuppressWarnings("unused") String actualFailedTestInRank : actualFailedTestsInRank) {
					// relative position of this failure WRT other tests excluding other actual failed tests
					relativeRanks.add(relativePosition);
//						if (VERBOSE) {
//							System.out.println("Test " + actualFailedTest + " ranked at #" + (rank + 1) + "/" + total + " = " + relativePosition + ", predictor=" + predictor.getName() + ", score=" + rank);
//						}
				}
			}
		}
	}
}
